/***********************************************************************

	This file is part of KEEL-software, the Data Mining tool for regression, 
	classification, clustering, pattern mining and so on.

	Copyright (C) 2004-2010
	
	F. Herrera (herrera@decsai.ugr.es)
    L. Sánchez (luciano@uniovi.es)
    J. Alcalá-Fdez (jalcala@decsai.ugr.es)
    S. García (sglopez@ujaen.es)
    A. Fernández (alberto.fernandez@ujaen.es)
    J. Luengo (julianlm@decsai.ugr.es)

	This program is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.

	This program is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with this program.  If not, see http://www.gnu.org/licenses/
  
**********************************************************************/

package keel.Algorithms.Neural_Networks.NNEP_Common.neuralnet;


import java.util.ArrayList;

import javolution.xml.XmlElement;
import javolution.xml.XmlFormat;

import org.apache.commons.lang.builder.HashCodeBuilder;

/**
 * <p>Implementation of a neural net
 * @author Written by Pedro Antonio Gutierrez Penya, Aaron Ruiz Mora (University of Cordoba) 17/07/2007
 * @version 0.1
 * @since JDK1.5
 * </p>
 */

public abstract class AbstractNeuralNet implements INeuralNet{
	
	/**
	 * <p>
	 * Implementation of a neural net
	 * </p>
	 */
	
    /////////////////////////////////////////////////////////////////
    // ------------------------------------- Marshal/unmarshal format
    /////////////////////////////////////////////////////////////////

	/**
	 * <p>
	 * Marshal/Unmarshal input layer, hidden layers and output layer
	 * </p>
	 */
	
	protected static final javolution.xml.XmlFormat<AbstractNeuralNet> XML = 
		new XmlFormat<AbstractNeuralNet>(AbstractNeuralNet.class) 
		{
			public void format(AbstractNeuralNet source, XmlElement xml) 
			{
				// Marshal input layer
				xml.add(source.inputLayer, "input-layer");
				// Marshal hidden layers
				xml.add(source.hiddenLayers, "hidden-layers");
				// Marshal output layers
				xml.add(source.outputLayer, "output-layer");
			}

			public AbstractNeuralNet parse(XmlElement xml) 
			{
				// Resulting object
				AbstractNeuralNet result = (AbstractNeuralNet) xml.object();
				// Unmarshal input layer
				result.inputLayer = xml.<InputLayer>get("input-layer");
				// Unmarshal hidden layers
				result.hiddenLayers = xml.<ArrayList<LinkedLayer>>get("hidden-layers");
				// Update neuron references
				ILayer<? extends INeuron> previousLayer = result.inputLayer;
				for(LinkedLayer hiddenLayer:result.hiddenLayers){					
					for(int i=0; i<hiddenLayer.getNofneurons();i++){
						Link [] links = hiddenLayer.getNeuron(i).getLinks();
						for(int j=0; j<links.length; j++){
							links[j].setTarget(hiddenLayer.getNeuron(i));
							if(!links[j].isBroken() && j<previousLayer.getMaxnofneurons())
								links[j].setOrigin(previousLayer.getNeuron(j));
							else
								links[j].setOrigin(null);
						}
					}
					previousLayer = hiddenLayer;
				}
				// Unmarshal output layer
				result.outputLayer = xml.<LinkedLayer>get("output-layer");
				// Update neuron references
				for(int i=0; i<result.outputLayer.getNofneurons();i++){
					Link [] links = ((LinkedNeuron)result.outputLayer.getNeuron(i)).getLinks();
					for(int j=0; j<links.length; j++){
						links[j].setTarget(result.outputLayer.getNeuron(i));
						if(!links[j].isBroken() && j<previousLayer.getMaxnofneurons())
							links[j].setOrigin(previousLayer.getNeuron(j));
						else
							links[j].setOrigin(null);
					}
				}
				// Return result
				return result;
			}

			public String defaultName() 
			{
				return "neural-net";
			}
		};
		
    /////////////////////////////////////////////////////////////////
    // --------------------------------------- Serialization constant
    /////////////////////////////////////////////////////////////////

	/** Generated by eclipse */
	
	private static final long serialVersionUID = -5162585162983545056L;
	
    /////////////////////////////////////////////////////////////////
    // ------------------------------------------ Protected Variables
    /////////////////////////////////////////////////////////////////
    
    /** Input layer */
    protected InputLayer inputLayer;
    
    /** Hidden layer */
    protected ArrayList<LinkedLayer> hiddenLayers = 
        new ArrayList<LinkedLayer>();
    
    /** Ouput layer */
    protected LinkedLayer outputLayer;
    
    /////////////////////////////////////////////////////////////////
    // -------------------------------------------------- Constructor
    /////////////////////////////////////////////////////////////////
    
    /**
     * <p>
     * Empty constructor
     * </p>
     */
    public AbstractNeuralNet() {
        super();
    }
    
    /////////////////////////////////////////////////////////////////
    // ---------------------------- Implementing INeuralNet interface
    /////////////////////////////////////////////////////////////////
    
    /**
     * <p>
     * Returns the number of hidden layers of the neural net
     * </p>
     * @return int Number of hidden layers
     */
    public int getNofhlayers() {
        return hiddenLayers.size();
    }
    
    /**
     * <p>
	 * Returns the input layer of this neural net
	 * </p>
	 * @return InputLayer Input layer of the net
	 */
    public InputLayer getInputLayer() {
        return inputLayer;
    }
    
    /**
     * <p>
	 * Sets the input layer of this neural net
	 * </p>
	 * @param inputLayer New input layer of the net
	 */
    public void setInputLayer(InputLayer inputLayer) {
        this.inputLayer = inputLayer;
    }
    
    /**
     * <p>
     * Adds a new layer to the neural net
     * </p>
     * @param layer New layer
     */
    public void addHlayer(LinkedLayer layer) {
        hiddenLayers.add(layer);
    }
    
    /**
     * <p>
     * Returns a specific hidden layer of the neural net
     * </p>
     * @param index Number of layer to return
     * @return LinkedLayer Hidden layer
     */
    public LinkedLayer getHlayer(int index) {
        return hiddenLayers.get(index);
    }
    
    /**
     * <p>
	 * Returns the output layer of this neural net
	 * </p>
	 * @return LinkedLayer Output layer of the net
	 */
    public LinkedLayer getOutputLayer() {
        return outputLayer;
    }
    
    /**
     * <p>
	 * Sets the output layer of this neural net
	 * </p>
	 * @param outputLayer New output layer of the net
	 */
    public void setOutputLayer(LinkedLayer outputLayer) {
        this.outputLayer = outputLayer;
    }
    
    /**
     * <p>
     * Checks if this neural net is equal to another
     * </p>
     * @param other Other neural net to compare
     * @return true if both neural nets are equals
     */
    public boolean equals(INeuralNet other){
        if(this.hashCode()!=other.hashCode())
            return false;
        else
            return true;
    }
    
    /**
     * <p>
	 * Returns an integer number that identifies the neural net
	 * </p>
	 * @return int Hashcode
	 */
    public int hashCode(){
        HashCodeBuilder hcb = new HashCodeBuilder(61, 67);
        hcb.append(outputLayer);
        return hcb.toHashCode();
    }
    
    /**
     * <p>
     * Checks if this neural net is full of neurons
     * </p>
     * @return true if the neural net is full of neurons
     */
    public boolean neuronsFull()
    {
        for(LinkedLayer layer:hiddenLayers){
            if(!layer.neuronsFull())
                return false;
        }
        return true;
    }
    
    /**
     * <p>
     * Checks if this neural net is empty of neurons
     * </p>
     * @return true if the neural net is empty of neurons
     */
    public boolean neuronsEmpty()
    {
        for(LinkedLayer layer:hiddenLayers){
            if(!layer.neuronsEmpty())
                return false;
        }
        return true;
    }
    
    /**
     * <p>
     * Checks if this neural net is full of links
     * </p>
     * @return true if the neural net is full of links
     */
    public boolean linksFull() {
    	
    	// If here is not hidden layers
    	if(hiddenLayers.size()==0) {
    		if( !outputLayer.linksFull(inputLayer) )
        		return false;
    	}
    	else { 
    		// First hidden layer with input layer
    		if ( !hiddenLayers.get(0).linksFull(inputLayer) )
    			return false;
    		
    		// Hidden layer with its previous hidden layer
    		for(int i=1; i<hiddenLayers.size(); i++)
    			if( !hiddenLayers.get(i).linksFull(hiddenLayers.get(i-1)) )
    				return false;
    		
    		// Output layer with the last hidden layer
    		if( !outputLayer.linksFull(hiddenLayers.get(hiddenLayers.size()-1)) )
        		return false;
    	}
        
        return true;
    }
    
    /**
     * <p>
     * Checks if this neural net is empty of links
     * </p>
     * @return true if the neural net is empty of links
     */
    public boolean linksEmpty()
    {
        for(LinkedLayer layer:hiddenLayers){
            if(!layer.linksEmpty())
                return false;
        }
        
        if(!outputLayer.linksEmpty())
            return false;
        
        return true;
    }
    
    /**
     * <p>
	 * Returns the number of hidden neurons of this neural net
	 * </p>
	 * @return int Number of hidden neurons
	 */
    public int getNofhneurons()
    {
        int nofhneurons = 0;
        for(LinkedLayer layer:hiddenLayers){
            nofhneurons += layer.getNofneurons();
        }
        return nofhneurons;
    }
    
    /**
     * <p>
	 * Returns the number of effective links of this neural net
	 * </p>
	 * @return int Number of effective links
	 */
    public int getNoflinks()
    {
        int noflinks = 0;
        for(LinkedLayer layer:hiddenLayers){
            noflinks += layer.getNoflinks();
        }
        noflinks += outputLayer.getNoflinks();
        return noflinks;
    }
    
    /**
     * <p>
	 * Keep relevant links, that is, those links whose weight is higher
	 * than certain number
	 * </p>
     * @param significativeWeight Significative weight
	 */
    public void keepRelevantLinks(double significativeWeight){
    	for(LinkedLayer layer:hiddenLayers){
    		layer.keepRelevantLinks(significativeWeight);
    	}
    	outputLayer.keepRelevantLinks(significativeWeight);
    }
    
    /**
     * <p>
     * Returns a string representation of the neural net
     * </p>
     * @return String Representation of the neural net
     */
    public String toString(){
        StringBuffer sb = new StringBuffer();
        for(int i=0; i<outputLayer.getNofneurons(); i++){
            String neuron = outputLayer.getNeuron(i).toString();
            neuron = neuron.replace("+-", "-");
            neuron = neuron.replace("-+", "-");
            neuron = neuron.replace("--", "+");
            
            // Add " " at the end of the string so the next step works correctly
            neuron+=" ";
            // Add a "\n" at the end of each node
            int count=0;
            for(int j=0; j<neuron.length(); j++) {
            	if(neuron.charAt(j)=='(')
            		count++;
            	if(neuron.charAt(j)==')') {
            		count--;
            		if(count==0)
            			neuron = neuron.substring(0, j+1)+"\n"+neuron.substring(j+2, neuron.length());
            	}
            }
            
            sb.append(neuron);
        }
        return sb.toString();
    }
    
    /**
     * <p>
     * Returns a copy of this neural net
     * </p>
     * @return INeuralNet Copy of this neural net
     */
    public INeuralNet copy(){
    	
    	// Resulting neural net
    	AbstractNeuralNet result = null;
    	
		try {
	    	result = this.getClass().newInstance();
	    	
	    	// Copy the input layer
	    	result.inputLayer = this.inputLayer.copy();
	    	
	    	// Copy hidden layers
	    	ILayer<? extends INeuron> lastCopiedLayer = result.inputLayer;
	    	for(LinkedLayer hl:this.hiddenLayers){
	    		// Copy hidden layer
	    		LinkedLayer hlr = hl.copy(lastCopiedLayer);
	    		result.addHlayer(hlr);
	    		
	    		// Update last copied layer
	    		lastCopiedLayer = hlr;
	    	}
	    	
	    	// Copy the output layer
	    	result.outputLayer = this.outputLayer.copy(lastCopiedLayer);
		}catch (InstantiationException e) {
			System.out.println("Instantiation Error " + e.getLocalizedMessage());
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			System.out.println("Illegal Access Error " + e.getLocalizedMessage());
			e.printStackTrace();
		}
    	return result;
    }
}

